﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Cubicle.Core.Interfaces;
using Cubicle.Core.Processors;
using Cubicle.Core.Shared;
using Cubicle.Excel.AddIn;
using Cubicle.Excel.Binding;
using Cubicle.Excel.Config;
using Cubicle.Excel.Hosting;
using ExcelDna.Integration;

namespace Cubicle.Excel.Processors
{
    [Export("Cubicle.Excel.Processors.DescriptionToElementBinder", typeof(IProcessor<object>))]
    [ExportMetadata("Name", "DescriptionToElementBinder")]
    [ExportMetadata("Description", "Converts a BindingDescription to a BindingElement.")]
    public class DescriptionToElementBinder : ProcessorQueue<object, BindingElement>
    {
        // Nlog
        private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

        protected override BindingElement ConvertTo(object input)
        {
            // All processing done by children
            try
            {
                return new BindingElement((BaseDescription)input);
            }
            catch (Exception e)
            {
                var name = input is BaseDescription ? ((BaseDescription) input).FunctionName : "Unknown";
                _logger.ErrorException(String.Format("Binding error. Name = {0}", name), e);
                return null;
            }
        }

        protected override object ConvertFrom(BindingElement input)
        {
            return input.BindingDescription;
        }
    }

    [Export("Cubicle.Excel.Processors.LocalRemoteClassifier", typeof(IProcessor<BindingElement>))]
    [ExportMetadata("Name", "LocalRemoteClassifier")]
    [ExportMetadata("Description", "Classifies a method binding as either local or remote.")]
    public class LocalRemoteClassifier : Processor<BindingElement>
    {

        public override BindingElement Process(BindingElement request)
        {
            var bd = request.BindingDescription;
            var cortex = CubicleAddIn.Engines.Get(bd.Engine);
            if (cortex == null)
                throw new LogException("Engine not found. Engine = {0}", bd.Engine);

            request.Engine = cortex;

            // Remove local flag if this is not local
            request.BindingDescription.IsRemote = !(cortex is LocalProxy);
            return request;
        }

    }

    [Export("Cubicle.Excel.Processors.RemoteClassifier", typeof(IProcessor<BindingElement>))]
    [ExportMetadata("Name", "RemoteClassifier")]
    [ExportMetadata("Description", "Communicates with a remote host to verify and resolve the binding target.")]
    public class RemoteClassifier : ConditionalProcessor<BindingElement>
    {
        // Nlog
        private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

        public override bool Condition(BindingElement input)
        {
            return input.BindingDescription.IsRemote;
        }

        public override BindingElement ConditionalProcess(BindingElement request)
        {
            // Retrieve the method details for the target
            var bd = request.BindingDescription;
            var methods = request.Engine.GetMethods(bd.Target).ToList();
            if (methods == null)
                throw new LogException("Target not found. Target = {0}", bd.Target);
            var candidates = methods.Where(m => m.Name == bd.MethodName);
            var count = candidates.Count();

            if (count == 0)
                throw new LogException("Failed to create binding element. Method not found. MethodName=" + bd.MethodName);
            
            if (count > 1)
                _logger.Warn("Binding overloaded method. Using MethodOffset. OverloadCount = {0}, MethodName = {1}, MethodOffset = {2}",
                   methods.Count(), bd.MethodName, bd.MethodOffset);

            // Verify method count and offset values
            if (count <= bd.MethodOffset)
                throw new LogException("Failed to create binding element. MethodOffset exceeds OverloadCount. MethodOffset={0}, OverloadCount = {1}",
                    bd.MethodOffset, count);
            var method = candidates.Where(m => m.Offset == bd.MethodOffset).FirstOrDefault();
            request.RemoteMethod = method;
            request.TargetHandle = bd.Target;

            // Get the parameters
            request.RemoteParameters = request.Engine.GetParameterData(bd.Target, method.Name, method.Offset).ToArray();

            // Get #parameters
            request.ParameterTypes = new Type[method.ParameterCount + 1];
            for (var i = 0; i < method.ParameterCount; i++)
                request.ParameterTypes[i] = typeof (object);
            request.ParameterTypes[method.ParameterCount] = typeof(object);
            request.ReturnType = typeof (object);
            return request;
        }

    }

    [Export("Cubicle.Excel.Processors.LocalClassifier", typeof(IProcessor<BindingElement>))]
    [ExportMetadata("Name", "LocalClassifier")]
    [ExportMetadata("Description", "Resolves the binding target on the local host")]
    public class LocalClassifier : ConditionalProcessor<BindingElement>
    {
        // Nlog
        private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

        public override bool Condition(BindingElement input)
        {
            return !input.BindingDescription.IsRemote;
        }

        #region Overrides of ProcessorBase<BindingElement>

        public override BindingElement ConditionalProcess(BindingElement request)
        {
            var bd = request.BindingDescription;

            // If this is local, determine static or virtual
            var isVirtual = request.Engine.Has(bd.Target);
            request.BindingDescription.IsStatic = !isVirtual;
            Type targetType;
            if (isVirtual)
            {
                request.TargetHandle = bd.Target;
                var obj = request.Engine.Get(bd.Target);
                request.TargetObject = obj;
                targetType = obj.GetType();
            }
            else
            {
                if (bd.Target == null)
                    throw new LogException("BindingElement.Target cannot be null!");
                targetType = Type.GetType(bd.Target, false);
                if (targetType == null)
                    throw new LogException("Failed to create binding element. Target not found. Target=" + bd.Target);
                // Also subtract flags for Object and Handle, since these are impossible for static methods
                //request.BindingType = request.BindingType.Subtract(BindType.Virtual | BindType.Object | BindType.Handle);
            }

            // Find the method
            var methods = targetType
                .GetMethods()
                .Where(m => m.Name == bd.MethodName)
                .ToArray();

            // Check if the method exists
            if (methods.Count() == 0)
                throw new LogException("Failed to create binding element. Method not found. MethodName=" + bd.MethodName);

            // Check if the method is overloaded
            if (methods.Count() > 1)
                _logger.Warn("Binding overloaded method. Using MethodOffset. OverloadCount = {0}, MethodName = {1}, MethodOffset = {2}",
                    methods.Count(), bd.MethodName, bd.MethodOffset);

            // Check if the offset is valid
            if (methods.Count() <= bd.MethodOffset)
                throw new LogException("Failed to create binding element. MethodOffset exceeds OverloadCount. MethodOffset={0}, OverloadCount = {1}",
                    bd.MethodOffset, methods.Count());
            var method = methods[bd.MethodOffset];
            request.Method = method;
            return request;
        }

        #endregion
    }

    [Export("Cubicle.Excel.Processors.LocalIndirectTypeReflector", typeof(IProcessor<BindingElement>))]
    [ExportMetadata("Name", "LocalIndirectTypeReflector")]
    [ExportMetadata("Description", "Reflect local binding target parameter types")]
    public class LocalIndirectTypeReflector : ConditionalProcessor<BindingElement>
    {

        public override bool Condition(BindingElement input)
        {
            return !input.BindingDescription.IsRemote 
                && input.BindingDescription.ResolveParameters;
        }

        public override BindingElement ConditionalProcess(BindingElement request)
        {
            // Determine parameter & return types
            var returnType = typeof(object);
            var pars = new Type[request.Method.GetParameters().Length + 1];
            for (var i = 0; i < pars.Length - 1; i++)
                pars[i] = typeof(object);
            pars[pars.Length - 1] = typeof(object);
            request.ReturnType = returnType;
            request.ParameterTypes = pars;
            return request;
        }

    }

    [Export("Cubicle.Excel.Processors.LocalDirectTypeReflector", typeof(IProcessor<BindingElement>))]
    [ExportMetadata("Name", "LocalDirectTypeReflector")]
    [ExportMetadata("Description", "Reflect local binding target parameter types")]
    public class LocalDirectTypeReflector : ConditionalProcessor<BindingElement>
    {

        public override bool Condition(BindingElement input)
        {
            return !input.BindingDescription.IsRemote 
                && !input.BindingDescription.ResolveParameters;
        }

        #region Overrides of ProcessorBase<BindingElement>

        public override BindingElement ConditionalProcess(BindingElement request)
        {
            // TODO: Update to reflect actual parameter types
            var returnType = typeof(object);
            var pars = new Type[request.Method.GetParameters().Length + 1];
            for (var i = 0; i < pars.Length - 1; i++)
                pars[i] = typeof(object);
            pars[pars.Length - 1] = typeof(object);
            request.ReturnType = returnType;
            request.ParameterTypes = pars;
            return request;
        }

        #endregion
    }

    [Export("Cubicle.Excel.Processors.RemoteHelper", typeof(IProcessor<BindingElement>))]
    [ExportMetadata("Name", "RemoteHelper")]
    [ExportMetadata("Description", "Invocation helper for method calls to remote host")]
    public class RemoteHelper : ConditionalProcessor<BindingElement>
    {

        public override bool Condition(BindingElement input)
        {
            return input.BindingDescription.IsRemote
                && !input.BindingDescription.Async;
        }

        public static object Helper(int id, object[] pars, object key)
        {
            // Convert missing values to nulls
            pars = pars.Select(x => x == ExcelMissing.Value ? null : x).ToArray();
            var keyStr = (key == ExcelMissing.Value) ? null : key.ToString();

            // Proceed with binding
            var be = BindManager.Bindings[id];
            var bd = be.BindingDescription;
            var target = be.TargetHandle;
            var inst = new Instruction(target, bd.MethodName, keyStr, pars, null, null);
            var result = be.Engine.Process(inst);
            be.IncrementInvokeCount();
            return result;
        }

        #region Overrides of ProcessorBase<BindingElement>

        public override BindingElement ConditionalProcess(BindingElement request)
        {
            request.Helper = this.GetType().GetMethod("Helper");
            return request;
        }

        #endregion
    }

    [Export("Cubicle.Excel.Processors.LocalStaticIndirectSyncHelper", typeof(IProcessor<BindingElement>))]
    [ExportMetadata("Name", "LocalStaticIndirectSyncHelper")]
    [ExportMetadata("Description", "Invocation helper. Host type = local, Method type = static, Target = object, Parameter resolution = true")]
    public class LocalStaticIndirectSyncHelper : ConditionalProcessor<BindingElement>
    {

        public override bool Condition(BindingElement input)
        {
            return !input.BindingDescription.IsRemote
                && input.BindingDescription.IsStatic
                && input.BindingDescription.ResolveParameters
                && !input.BindingDescription.Async;
        }

        public static object Helper(int id, object[] pars, object key)
        {
            var keyStr = (key == ExcelMissing.Value) ? null : key.ToString();
            var be = BindManager.Bindings[id];
            var bd = be.BindingDescription;
            var inst = new Instruction(bd.Target, bd.MethodName, keyStr, pars, null, null);
            var result = be.Engine.Process(inst);
            be.IncrementInvokeCount();
            return result;
        }

        #region Overrides of ProcessorBase<BindingElement>

        public override BindingElement ConditionalProcess(BindingElement request)
        {
            request.Helper = this.GetType().GetMethod("Helper");
            return request;
        }

        #endregion
    }

    [Export("Cubicle.Excel.Processors.LocalStaticIndirectAsyncHelper", typeof(IProcessor<BindingElement>))]
    [ExportMetadata("Name", "LocalStaticIndirectAsyncHelper")]
    [ExportMetadata("Description", "Invocation helper. Host type = local, Method type = static, Target = object, Parameter resolution = true")]
    public class LocalStaticIndirectAsyncHelper : ConditionalProcessor<BindingElement>
    {

        public override bool Condition(BindingElement input)
        {
            return !input.BindingDescription.IsRemote 
                && input.BindingDescription.IsStatic 
                && input.BindingDescription.ResolveParameters
                && input.BindingDescription.Async;
        }

        public static object Helper(int id, object[] pars, object key)
        {
            var keyStr = (key == ExcelMissing.Value) ? null : key.ToString();
            var be = BindManager.Bindings[id];
            var bd = be.BindingDescription;
            var inst = new Instruction(bd.Target, bd.MethodName, keyStr, pars, null, null);
            be.IncrementInvokeCount();
            return ExcelAsyncUtil.Run(bd.Name, pars, () => be.Engine.Process(inst));
        }

        #region Overrides of ProcessorBase<BindingElement>

        public override BindingElement ConditionalProcess(BindingElement request)
        {
            request.Helper = this.GetType().GetMethod("Helper");
            return request;
        }

        #endregion
    }

    [Export("Cubicle.Excel.Processors.LocalVirtualHandleDirectHelper", typeof(IProcessor<BindingElement>))]
    [ExportMetadata("Name", "LocalVirtualHandleDirectHelper")]
    [ExportMetadata("Description", "Invocation helper. Host type = local, Method type = instance, Target = handle, Parameter resolution = false")]
    public class LocalVirtualHandleDirectHelper : ConditionalProcessor<BindingElement>
    {

        public override bool Condition(BindingElement input)
        {
            return !input.BindingDescription.IsRemote
                && !input.BindingDescription.IsStatic
                && !input.BindingDescription.ResolveParameters
                && input.BindingDescription.BindToHandle
                && !input.BindingDescription.Async;
        }

        public static object Helper(int id, object[] pars, object key)
        {
            // Retrieve the binding info
            var be = BindManager.Bindings[id];

            // Find target object
            var target = be.Engine.Get(be.TargetHandle);
            if (target == null) return null;

            // Execute Fast Invoker on the object and parameters
            var result = be.Indirection.Invoke(null, new[] { target, pars });

            // Increment counter
            be.IncrementInvokeCount();

            // Only add to repository if the name is not empty
            var keyStr = (key == ExcelMissing.Value) ? null : key.ToString();
            return keyStr == ""
                ? result
                : CubicleAddIn.Engines.DefaultClient.Set(keyStr, result);

        }

        #region Overrides of ProcessorBase<BindingElement>

        public override BindingElement ConditionalProcess(BindingElement request)
        {
            request.Helper = this.GetType().GetMethod("Helper");
            return request;
        }

        #endregion
    }

    [Export("Cubicle.Excel.Processors.LocalVirtualHandleIndirectHelper", typeof(IProcessor<BindingElement>))]
    [ExportMetadata("Name", "LocalVirtualHandleIndirectHelper")]
    [ExportMetadata("Description", "Invocation helper. Host type = local, Method type = instance, Target = handle, Parameter resolution = true")]
    public class LocalVirtualHandleIndirectHelper : ConditionalProcessor<BindingElement>
    {

        public override bool Condition(BindingElement input)
        {
            return !input.BindingDescription.IsRemote
                && !input.BindingDescription.IsStatic
                && input.BindingDescription.ResolveParameters
                && input.BindingDescription.BindToHandle
                && !input.BindingDescription.Async;
        }

        public static object Helper(int id, object[] pars, object key)
        {
            var keyStr = (key == ExcelMissing.Value) ? null : key.ToString();
            var be = BindManager.Bindings[id];
            var bd = be.BindingDescription;
            var target = be.TargetHandle;
            pars = Shared.ExcelHelpers.DoubleToJaggedOrArrayConvert(pars);
            var inst = new Instruction(target, bd.MethodName, keyStr, pars);
            var result = be.Engine.Process(inst);
            be.IncrementInvokeCount();
            
            // Value type arrays must be cast back to object arrays
            var resultType = result.GetType();
            if (!resultType.IsArray || resultType.GetElementType() == typeof(object)) 
                return result;

            return ((Array) result).Cast<object>().ToArray();
        }

        #region Overrides of ProcessorBase<BindingElement>

        public override BindingElement ConditionalProcess(BindingElement request)
        {
            request.Helper = this.GetType().GetMethod("Helper");
            return request;
        }

        #endregion
    }

    [Export("Cubicle.Excel.Processors.LocalVirtualObjectDirectHelper", typeof(IProcessor<BindingElement>))]
    [ExportMetadata("Name", "LocalVirtualObjectDirectHelper")]
    [ExportMetadata("Description", "Invocation helper. Host type = local, Method type = instance, Target = object, Parameter resolution = false")]
    public class LocalVirtualObjectDirectHelper : ConditionalProcessor<BindingElement>
    {

        public override bool Condition(BindingElement input)
        {
            return !input.BindingDescription.IsRemote               // Local
                && !input.BindingDescription.IsStatic               // Virtual
                && !input.BindingDescription.ResolveParameters      // Direct
                && !input.BindingDescription.BindToHandle           // Object
                && !input.BindingDescription.Async;                 // Sync
        }

        public static object Helper(int id, object[] pars, object key)
        {
            // Retrieve the binding info
            var be = BindManager.Bindings[id];

            // Execute Fast Invoker on the object and parameters
            var result = be.Indirection.Invoke(null, new[] { be.TargetObject, pars });

            // Increment counter
            be.IncrementInvokeCount();

            // Only add to repository if the name is not empty
            var keyStr = (key == ExcelMissing.Value) ? null : key.ToString();
            return keyStr == ""
                ? result
                : CubicleAddIn.Engines.DefaultClient.Set(keyStr, result);
        }

        #region Overrides of ProcessorBase<BindingElement>

        public override BindingElement ConditionalProcess(BindingElement request)
        {
            request.Helper = this.GetType().GetMethod("Helper");
            return request;
        }

        #endregion
    }

    [Export("Cubicle.Excel.Processors.LocalVirtualObjectIndirectHelper", typeof(IProcessor<BindingElement>))]
    [ExportMetadata("Name", "LocalVirtualObjectIndirectHelper")]
    [ExportMetadata("Description", "Invocation helper. Host type = local, Method type = instance, Target = object, Parameter resolution = true")]
    public class LocalVirtualObjectIndirectHelper : ConditionalProcessor<BindingElement>
    {

        public override bool Condition(BindingElement input)
        {
            return !input.BindingDescription.IsRemote               // Local
                && !input.BindingDescription.IsStatic               // Virtual
                && input.BindingDescription.ResolveParameters       // Indirect
                && !input.BindingDescription.BindToHandle           // Object
                && !input.BindingDescription.Async;                 // Sync
        }

        public static object Helper(int id, object[] pars, object key)
        {
            // Retrieve the binding info
            var be = BindManager.Bindings[id];

            // Retrieve Engine
            var cortex = be.Engine;

            // Resolve parameters manually
            for (var i = 0; i < pars.Length; i++)
            {
                var par = pars[i];
                if (!(par is string)) continue;
                var parStr = (string)par;
                if (cortex.Has(parStr))
                    pars[i] = cortex.Get(parStr);
            }

            // Execute Fast Invoker on the object and parameters
            var result = be.Indirection.Invoke(null, new[] { be.TargetObject, pars });

            // Increment counter
            be.IncrementInvokeCount();

            // Only add to repository if the name is not empty
            var keyStr = (key == ExcelMissing.Value) ? null : key.ToString();
            return keyStr == ""
                ? result
                : CubicleAddIn.Engines.DefaultClient.Set(keyStr, result);
        }

        #region Overrides of ProcessorBase<BindingElement>

        public override BindingElement ConditionalProcess(BindingElement request)
        {
            request.Helper = this.GetType().GetMethod("Helper");
            return request;
        }

        #endregion
    }

    [Export("Cubicle.Excel.Processors.FastInvokeCreator", typeof(IProcessor<BindingElement>))]
    [ExportMetadata("Name", "FastInvokeCreator")]
    [ExportMetadata("Description", "Emit a fast invoker.")]
    public class FastInvokeCreator : ConditionalProcessor<BindingElement>
    {

        public override bool Condition(BindingElement input)
        {
            return input.BindingDescription.IsFastBind();
        }

        public override BindingElement ConditionalProcess(BindingElement request)
        {
            request.Indirection = MakeFastInvoker(request);
            return request;
        }

        public static DynamicMethod MakeFastInvoker(BindingElement request)
        {
            var dynamicMethod = new DynamicMethod(
                string.Empty,
                typeof(object),
                new[] { typeof(object), typeof(object[]) },
                request.Method.DeclaringType);
            ILGenerator il = dynamicMethod.GetILGenerator();

            // Infer parameter types
            Type[] paramTypes = GetParamTypes(request.Method, false);

            // Declare a local variable for each parameter with the correct type
            LocalBuilder[] locals = new LocalBuilder[paramTypes.Length];
            for (int i = 0; i < paramTypes.Length; i++)
            {
                locals[i] = il.DeclareLocal(paramTypes[i]);
            }

            // Cast parameters to appropriate types and store in local variables
            for (int i = 0; i < paramTypes.Length; i++)
            {
                il.Emit(OpCodes.Ldarg_1);
                EmitHelpers.EmitInt(il, i);
                il.Emit(OpCodes.Ldelem_Ref);
                EmitHelpers.EmitCast(il, paramTypes[i]);
                il.Emit(OpCodes.Stloc, locals[i]);
            }

            // Load the target object of the invocation, if this is an instance call
            if (!request.Method.IsStatic)
                il.Emit(OpCodes.Ldarg_0);

            // Load all the arguments to the invoke
            for (int i = 0; i < paramTypes.Length; i++)
                il.Emit(OpCodes.Ldloc, locals[i]);

            // Call the method on cast parameters
            //if (methodInfo.IsVirtual)
            //    il.EmitCall(OpCodes.Callvirt, methodInfo, null);
            //else
            il.EmitCall(OpCodes.Call, request.Method, null);

            // Return result
            if (request.Method.ReturnType == typeof(void))
                il.Emit(OpCodes.Ldnull);
            else
                EmitHelpers.EmitBoxing(il, request.Method.ReturnType);
            il.Emit(OpCodes.Ret);

            return dynamicMethod;
        }

        private static Type[] GetParamTypes(MethodInfo mi, bool appendString)
        {
            var pars = mi.GetParameters();
            var parTypes = new Type[pars.Length + (appendString ? 1 : 0)];
            for (var i = 0; i < pars.Length; i++)
                if (pars[i].ParameterType.IsByRef)
                    parTypes[i] = pars[i].ParameterType.GetElementType();
                else
                    parTypes[i] = pars[i].ParameterType;

            if (appendString)
                parTypes[pars.Length] = typeof(string);

            return parTypes;
        }

    }

    [Export("Cubicle.Excel.Processors.ProxyCreator", typeof(IProcessor<BindingElement>))]
    [ExportMetadata("Name", "ProxyCreator")]
    [ExportMetadata("Description", "Creates a static proxy for an instance method.")]
    public class ProxyCreator : ConditionalProcessor<BindingElement>
    {

        public override bool Condition(BindingElement input)
        {
            // Anything but Local Static Direct Object Sync
            return !input.BindingDescription.IsFastBind();
        }

        public override BindingElement ConditionalProcess(BindingElement request)
        {
            if (request.Helper == null) 
                throw new Exception("Cannot create wrapper method, no helper found!");
            request.Proxy = Wrap(request);
            return request;
        }

        /// <summary>
        /// TODO: Add support for generic class & method arguments
        /// 1) MethodInfo is bound with generic arguments
        /// 2) Generic arguments are supplied as input from user
        /// 
        /// This method will generate a dynamic wrapper for instance methods.
        /// A function binding ID is hard-coded in the body of the static method.
        /// This is used to find method and instance data. This way the static method 
        /// knows how to locate the appropriate instance object.
        /// 
        /// Most of the hard work is done by a helper method to limit the
        /// amount of CIL that have to be generated by hand.
        /// 
        /// Wrap will generate the following dynamic method.
        /// In this example 
        ///     "0" is the hardcoded function ID.
        ///     "resolveParameters" is a boolean indicator of whether this dynamic method 
        ///     should verify parameter substitution from the dictionary.
        /// 
        /// Parameter resolution is a relatively costly process so users might
        /// want to avoid this when possible.
        /// 
        /// private static object Emit(object p1, object p2, ..., object pn, object name)
        /// {
        ///     var p = new object[] { p1, p2, ..., pn }
        ///     return DirectHelper(true, 0, p, name);
        /// }
        /// </summary>
        /// <param name="request">The binding element to wrap in a dynamic method</param>
        /// <returns></returns>
        private static DynamicMethod Wrap(BindingElement request)
        {
            // Infer parameter types
            var functionName = request.BindingDescription.Name;
            var id = request.Id;
            var pars = request.BindingDescription.IsRemote
                ? null 
                : request.Method.GetParameters();
            var parCount = request.BindingDescription.IsRemote
                ? request.RemoteParameters.Length 
                : pars.Length;
            Type[] parTypes = request.ParameterTypes;

            /**************************************************************************\
             * Action      : Define dynamic method     
            \**************************************************************************/
            var returnType = request.ReturnType;
            var module = typeof(BindManager).Module;
            var dm = new DynamicMethod(
                functionName,
                MethodAttributes.Static | MethodAttributes.Public,
                CallingConventions.Standard,
                returnType,
                parTypes,
                module, //mi.DeclaringType,
                true);
            
            /**************************************************************************\
             * Action      : Define parameters     
            \**************************************************************************/
            for (var i = 0; i < parCount; i++)
                if (request.BindingDescription.IsRemote)
                    dm.DefineParameter(i + 1, ParameterAttributes.None, request.RemoteParameters[i].Name);
                else
                    dm.DefineParameter(i + 1, pars[i].Attributes, pars[i].Name);

            // Add a parameter for the name to use if storing the result
            // This will cause an error if any of the other parameters are also names "__key"
            dm.DefineParameter(parCount + 1, ParameterAttributes.None, "__key");

            // TODO: Add parameters for generic method arguments


            // TODO: Add parameters for generic class arguments


            // Get IL Generator
            var ilg = dm.GetILGenerator();

            /**************************************************************************\
             * Action      : Declare result and parameter array     
            \**************************************************************************/
            ilg.DeclareLocal(typeof(object));       // loc0 = object result 
            ilg.DeclareLocal(typeof(object[]));     // loc1 = object[] p

            /**************************************************************************\
             * Action      : Declare result and parameter array     
            \**************************************************************************/
            //            EmitInt(ilg, resolveParams ? 1 : 0);        // Push | bool resolveParams
            EmitHelpers.EmitInt(ilg, id);                           // Push | int methodId
            EmitHelpers.EmitInt(ilg, parTypes.Length - 1);            // Code | p = new object[pars.Length];
            ilg.Emit(OpCodes.Newarr, typeof(object));
            ilg.Emit(OpCodes.Stloc_1);

            for (int i = 0; i < parCount; i++)       // Code | p[i] = parameter_i, i = 0,..,n
            {
                ilg.Emit(OpCodes.Ldloc_1);
                EmitHelpers.EmitInt(ilg, i);
                EmitHelpers.EmitArg(ilg, i);
                EmitHelpers.EmitBoxing(ilg, parTypes[i]);
                ilg.Emit(OpCodes.Stelem_Ref);
            }

            ilg.Emit(OpCodes.Ldloc_1);                  // Push | p
            EmitHelpers.EmitArg(ilg, parCount);                  // Push | name
            ilg.EmitCall(OpCodes.Call, request.Helper, null);   // Push | DirectHelper(resolveParams, objectHandle, methodId, p, name)
            ilg.Emit(OpCodes.Ret);

            return dm;
        }

    }

    [Export("Cubicle.Excel.Processors.ExcelMethodInfoCreator", typeof(IProcessor<BindingElement>))]
    [ExportMetadata("Name", "ExcelMethodInfoCreator")]
    [ExportMetadata("Description", "Prepares method meta-data before binding.")]
    public class ExcelMethodInfoCreator : Processor<BindingElement>
    {

        public override BindingElement Process(BindingElement request)
        {
            // Make ExcelMethodInfo
            var bd = request.BindingDescription;
            var emi = new ExcelMethodInfo();

            // For direct statically bound methods, call the method directly
            emi.TargetMethod = request.BindingDescription.IsFastBind()
                ? request.Method
                : request.Proxy;
            emi.Description = bd.Description;
            emi.IsExceptionSafe = bd.IsExceptionSafe;
            emi.Name = bd.Name;

            if (bd is FunctionDescription)
            {
                var fd = (FunctionDescription)bd;
                emi.IsHidden = fd.IsHidden;
                emi.IsThreadSafe = fd.IsThreadSafe;
                emi.Category = String.IsNullOrEmpty(fd.Category)
                                   ? request.Method.DeclaringType.Assembly.GetName().Name
                                   : fd.Category;
                emi.IsVolatile = fd.IsVolatile;
                emi.IsMacroType = fd.IsMacroType;
            }
            else
            {
                var cd = (CommandDescription)bd;
                emi.MenuName = cd.MenuName;
                emi.MenuText = cd.MenuText;
            }
            request.Emi = emi;
            return request;
        }

    }

    [Export("Cubicle.Excel.Processors.BindRegister", typeof(IProcessor<BindingElement>))]
    [ExportMetadata("Name", "BindRegister")]
    [ExportMetadata("Description", "Executes binding and logs binding details.")]
    public class BindRegister : Processor<BindingElement>
    {
        // Nlog
        private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

        #region Overrides of ProcessorBase<BindingElement>

        public override BindingElement Process(BindingElement request)
        {
            if (request.Emi == null)
            {
                _logger.Info("Failed to bind. Method not found.");
                return request;
            }

            // Bind method
            var bd = request.BindingDescription;
            ExcelAsyncUtil.QueueAsMacro(() =>
            {
                //ExcelIntegration.RegisterExcelMethods(new List<ExcelMethodInfo>() {request.Emi});
                ExcelIntegration.RegisterMethods(new List<MethodInfo>() {request.Emi.TargetMethod}); // ExcelDna does not support Emi in v0.30
                _logger.Info("Successful function binding. Target={0}, Method={1}, IsRemote={2}, IsStatic={3}, ResolveParameters={4}, BindToHandle={5}, Async={6}",
                    bd.Target, 
                    bd.MethodName, 
                    bd.IsRemote, 
                    bd.IsStatic, 
                    bd.ResolveParameters, 
                    bd.BindToHandle, 
                    bd.Async);

                // Save binding info
                BindManager.Bindings[request.Id] = request;
            });
            return request;
        }

        #endregion
    }

}
